package com.jboltai.capability.text.extract.docx;

import cn.hutool.core.util.StrUtil;
import com.jboltai.event.text.TextEvent;
import com.jboltai.util.text.FileInfo;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.Ole10Native;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTObject;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.List;
import java.util.function.Consumer;

public class DocxExtractor {

    public static void extract(File file, Consumer<String> chunkConsumer, TextEvent textEvent) {
        try (FileInputStream fis = new FileInputStream(file)) {
            OPCPackage pkg = OPCPackage.open(fis);
            XWPFDocument document = new XWPFDocument(pkg);
            List<IBodyElement> elements = document.getBodyElements();

            for (IBodyElement element : elements) {
                if (element instanceof XWPFParagraph) {
                    XWPFParagraph para = (XWPFParagraph) element;
                    processParagraph(para, chunkConsumer, textEvent);
                } else if (element instanceof XWPFTable) {
                    XWPFTable table = (XWPFTable) element;
                    processTable(table, chunkConsumer);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void processParagraph(XWPFParagraph para, Consumer<String> chunkConsumer, TextEvent textEvent) {
        StringBuilder paraText = new StringBuilder();

        // Handle numbering and bullets
        if (para.getNumID() != null) {
            int level = para.getNumIlvl() != null ? para.getNumIlvl().intValue() : 0;
            String indent = StrUtil.repeat("    ", level); // Basic indentation based on level

            // Get numbering format
            XWPFNumbering numbering = para.getDocument().getNumbering();
            if (numbering != null) {
                BigInteger numId = para.getNumID();
                XWPFNum num = numbering.getNum(numId);
                if (num != null) {
                    BigInteger abstractNumId = num.getCTNum().getAbstractNumId().getVal();
                    XWPFAbstractNum abstractNum = numbering.getAbstractNum(abstractNumId);
                    if (abstractNum != null) {
                        // Get the level format
                        String numFormat = abstractNum.getAbstractNum().getLvlList().get(level).getNumFmt().getVal().toString();
                        String levelText = abstractNum.getAbstractNum().getLvlList().get(level).getLvlText().getVal();

                        // Get the actual number for this paragraph
                        ListItem currentItem = new ListItem(numId.intValue(), level);
                        int actualNum = getListNumber(para, currentItem);

                        // If it's using Chinese characters or numbers, keep as is
                        if (levelText.matches(".*[\\u4e00-\\u9fa5].*") || "decimal".equals(numFormat)) {
                            String numberText = levelText.replace("%1", String.valueOf(actualNum));
                            paraText.append(indent).append(numberText).append(" ");
                        } else {
                            // Convert special bullets to appropriate format
                            if ("bullet".equals(numFormat)) {
                                paraText.append(indent).append("• ");
                            } else {
                                // For other numbering styles, use default numbering
                                paraText.append(indent).append(actualNum).append(". ");
                            }
                        }
                    }
                }
            }
        }

        for (XWPFRun run : para.getRuns()) {
            String text = run.text();
            if (StrUtil.isNotBlank(text)) {
                paraText.append(text.replaceAll("\\^\\[\\d+\\]\\^", ""));
            }

            if (textEvent.getFile2TextConverter() != null) {
                // Process embedded pictures
                List<XWPFPicture> pics = run.getEmbeddedPictures();
                if (pics != null) {
                    for (XWPFPicture pic : pics) {
                        XWPFPictureData picData = pic.getPictureData();
                        if (picData == null) continue;

                        try (ByteArrayInputStream is = new ByteArrayInputStream(picData.getData())) {
                            FileInfo fileInfo = new FileInfo(picData.getFileName(), is);
                            String fileConvertText = textEvent.getFile2TextConverter().convert(textEvent, fileInfo);
                            if (StrUtil.isNotBlank(fileConvertText)) {
                                paraText.append("\n").append(fileConvertText);
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }

                // Process embedded objects (like PDFs, other documents)
                for (CTObject ctObject : run.getCTR().getObjectList()) {
                    NodeList childs = ctObject.getDomNode().getChildNodes();
                    for (int i = 0; i < childs.getLength(); i++) {
                        Node node = childs.item(i);
                        if (node != null && node.getNodeName().equalsIgnoreCase("o:OLEObject")) {
                            NamedNodeMap namedNodeMap = node.getAttributes();
                            String rId = namedNodeMap.getNamedItem("r:id").getNodeValue();
                            try {
                                PackagePart packagePart = para.getDocument().getPartById(rId);
                                if (packagePart != null) {
                                    FileInfo embedFile = getFile(packagePart.getInputStream());
                                    if (embedFile != null && embedFile.getFileName() != null) {
                                        String fileConvertText = textEvent.getFile2TextConverter().convert(textEvent, embedFile);
                                        if (StrUtil.isNotBlank(fileConvertText)) {
                                            paraText.append("\n").append(fileConvertText);
                                        }
                                    }
                                }
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            }
        }

        if (paraText.length() > 0) {
            chunkConsumer.accept(paraText.toString() + "\n");
        }
    }

    private static void processTable(XWPFTable table, Consumer<String> chunkConsumer) {
        StringBuilder tableText = new StringBuilder();

        // Process table rows
        List<XWPFTableRow> rows = table.getRows();
        for (int i = 0; i < rows.size(); i++) {
            XWPFTableRow row = rows.get(i);
            List<XWPFTableCell> cells = row.getTableCells();

            // Convert cells to pipe-separated format for better AI readability
            for (int j = 0; j < cells.size(); j++) {
                XWPFTableCell cell = cells.get(j);
                String cellText = cell.getText().trim();
                tableText.append("|").append(cellText);
            }
            tableText.append("|\n");

            // Add separator line after header
            if (i == 0) {
                for (int j = 0; j < cells.size(); j++) {
                    tableText.append("|---");
                }
                tableText.append("|\n");
            }
        }

        chunkConsumer.accept(tableText.toString() + "\n");
    }

    public static FileInfo getFile(InputStream i) {
        try (POIFSFileSystem fs = new POIFSFileSystem(i)) {
            DirectoryNode root = fs.getRoot();
            if (root.hasEntry(Ole10Native.OLE10_NATIVE) == false) {
                //todo 如果插入的附件是excel、word、ppt, 这里无法解析，需要寻找别的解决方案
                return null;
            }

            Ole10Native ole = Ole10Native.createFromEmbeddedOleObject(root);
            byte[] data = ole.getDataBuffer();
            try (ByteArrayInputStream is = new ByteArrayInputStream(data)) {
                return new FileInfo(ole.getLabel2(), is);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    // Helper method to convert numbers to Roman numerals
    private static String toRoman(int number) {
        String[] thousands = {"", "M", "MM", "MMM"};
        String[] hundreds = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
        String[] tens = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
        String[] ones = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};

        return thousands[number/1000] +
               hundreds[(number%1000)/100] +
               tens[(number%100)/10] +
               ones[number%10];
    }

    private static class ListItem {
        private final int numId;
        private final int ilvl;

        public ListItem(int numId, int ilvl) {
            this.numId = numId;
            this.ilvl = ilvl;
        }
    }

    private static int getListNumber(XWPFParagraph paragraph, ListItem currentItem) {
        int num = 1;
        XWPFDocument doc = paragraph.getDocument();
        List<IBodyElement> elements = doc.getBodyElements();

        // Find the position of current paragraph
        int currentParaPos = elements.indexOf(paragraph);
        if (currentParaPos == -1) return num;

        // Count previous paragraphs with same numId and level
        for (int i = 0; i < currentParaPos; i++) {
            IBodyElement element = elements.get(i);
            if (element instanceof XWPFParagraph) {
                XWPFParagraph para = (XWPFParagraph) element;
                if (para.getNumID() != null && para.getNumIlvl() != null) {
                    int paraNumId = para.getNumID().intValue();
                    int paraLevel = para.getNumIlvl().intValue();

                    if (paraNumId == currentItem.numId && paraLevel == currentItem.ilvl) {
                        num++;
                    } else if (paraNumId == currentItem.numId && paraLevel < currentItem.ilvl) {
                        // Reset numbering when encountering parent level
                        num = 1;
                    }
                }
            }
        }
        return num;
    }
}
